#!/usr/bin/python -tt
# Paul Grieco
# 2011.06.04
# A quick module to get distances from google, right now the lat lons of interest
# are hard coded, the next step is to read them in from files, need to work out
# format.
#
# 2011.06.14
# Adding capcity to read list of lat longs to file and output results to a tab delimited 
# file.

"""
This is an attempt to use python to query for road distances between a matrix of 
latitude longitudes. We'll start with everything hard coded and try to build out.
"""
import sys
import time
import urllib
import urlparse
import simplejson as json
#import scipy.io

BASE_URL = 'http://maps.googleapis.com/maps/api/distancematrix/json?'

STATE_COL = (40.81684, -77.89395)
CARLISLE  = (40.20250, -77.19500)
PITTSBURGH = (40.44062,-79.99589)

def encodeLatLons(latlons):
  """
  Inputs:  latlons : list of lat lon tuples
  """
  ll_str =  [ '%f,%f' %(ll[0], ll[1]) for ll in latlons ]
  return '|'.join(ll_str)

def getDist(origins, dests):
  """
  Inputs: origins:  list of lat lon tuples
          dests:    list of lat lon tuples
  
  Constructs query string, get's result from google in json, and converts 
  json string to a python structure
  """
  o_str = 'origins=' + encodeLatLons(origins)
  d_str = 'destinations=' + encodeLatLons(dests)
  
  #Put Together the query, use mostly defaults for now...
  q_str = '&'.join([o_str, d_str, 'sensor=false'])
  url = BASE_URL + q_str
  result = json.load(urllib.urlopen(url))
  #result = urllib.urlopen(url)
  #scipy.io.savemat('outfile.mat', result)
  return result
  

def writeMatrix(res, fyle = sys.stdout, elem = ('distance','value')):
  """
  Inputs: res : result structure from Google obtained via getDist
          fyle: an open output file to write to (default standard out)
          elem: A tuple specifying which piece of data to write in the matrix
            options are ('distance', 'value'), ('distance', 'text')
            ('duration', 'value'), ('duration', 'text')
  
  Write's a tab-delimited text file flushes and returns.First row is destination 
  addys, first col is origin addresses
  
  NOTE: THIS FUNCTION IS NOT USED BY THE MAIN PROGRAM, IT'S TASK IS HANDLED
		ONE LINE AT A TIME USING singleOrigin BELOW
  """
  if not res['status'] == 'OK':
    print 'writeResult: Result Status is not OK!'
    return 1
 
 #Here we write the headder row:
  fyle.write('\t')
  for c in res['destination_addresses']:
     fyle.write(c + '\t')
  fyle.write('\n')
  
  #And this is the body, we used tab delimited because tabs don't appear in addresses...
  for (r, addy) in zip(res['rows'], res['origin_addresses']):
    fyle.write(addy + '\t')
    for c in r['elements']:
      fyle.write( '%d\t' % (float(c[elem[0]][elem[1]])) )
    fyle.write('\n')
  
  #Flush the buffer before we leave
  fyle.flush()

def singleOrigin(originwId, dests, fyle = sys.stdout, destIds = False, elem = ('distance','value')):
  """
  Inputs: originwId: (id, (lat,lon)) tuple
          dests: list of destinatins (no ids) in (lat, lon) tuple
          fyle: output file
          elem: element to write to file
          
  This function takes a single origin and id string and writes a single line to fyle
  with that origin id, address, and distances to each destination in order.
  """
  res = getDist([ originwId[1] ], dests)
  
  if not res['status'] == 'OK':
    return res['status']

  #If this is the first element, we will write the destinations as a header
  if destIds:
    fyle.write('Origin ID \t Origin Address \t')
    for c in destIds:
      fyle.write(c + '\t')
    fyle.write('\n\t\t')
    for c in res['destination_addresses']:
      fyle.write(c.encode('UTF-8') + '\t')
    fyle.write('\n')
  
  #Here is the code to write the matrix, not it treats a single element as a single-row
  #matrix because that is what google returns...
  try:
    fyle.write(originwId[0] + '\t' + res['origin_addresses'][0].encode('UTF-8') + '\t')    
    for c in res['rows'][0]['elements']:
      if c['status'] == 'OK':
        fyle.write( '%d\t' % (float(c[elem[0]][elem[1]])) )
      else:
        fyle.write( '-1\t' )
  
  except KeyError:
    print "Encountered Key Error: Res = \n"
    print res
    return "Key Error writing Result"
  
  fyle.write('\n')
  fyle.flush()
  return 0

def readProjList(fname, cols = (0,1,2)):
  """
  Input: fname, a file name for the list of projects
      
  Takes a file name, opens that file, and extracts the list of {id, Lat, Lon} for 
      each row of the file. The file format is
         id,manfirm,lat,lon
      but we ignore manufacturer in this code. 
  """
  try:
     f = open(fname, 'rU')
  except IOError:
    print "Could not open file: %s" % (fname)
    sys.exit()
  
  ids = []
  places = []
  
  try:
    #Read the first line of the file, just header information...
    f.readline()
    #Now loop through the rest....
    for line in f:
      cparse = line.split(',')
      ids.append(cparse[cols[0]])
      places.append( ( float(cparse[cols[1]]) , float(cparse[cols[2]]) ) )
  except ValueError or IndexError:
     print "File %s is not correctly formatted." % (fname)
  
  return (ids, places)

def main():
  """
  Right now, this is just a stub, we'll need to read in origin and destinatin
  files and handle calling getDist with appropriately sized request (google only
  allows so many requests per query, and day)
  """
  
  #Junk lists for testing...
  origins = [ STATE_COL, CARLISLE ]
  dests = [ CARLISLE, PITTSBURGH ]      
  
  # Check argument list
  args = sys.argv[1:]
    
  if not len(args) >= 3:
    print '%s: usage: [--skip <int>] <projfilename> <manfilename> <outputfilename>' % (sys.argv[0])
    sys.exit()
  
  skip = 0
  if args[0] == '--skip':
    skip = int(args[1])
    del args[0:2]

  
  #Read in Origins and Destinations Data...
  (oIds, origins) = readProjList(args[0], (0,2,3)) 
  (dIds, dests) = readProjList(args[1])
  
  #Google limits distance requests to 100 per 10s and 2500 per day
  per10s = 100/len(dIds)
  perDay = 2500/len(dIds)
  
  #Open outfile for writing:
  try:
    outfile = open(args[2], 'w')
  except IOError:
    print "Could not open file: %s" % (fname)
    sys.exit()

  #For the first element, we want to also write the destination header, so handle
  #it seperately and delete these elements from the origins lists: 
  if not skip:
    singleOrigin((oIds[0], origins[0]), dests, outfile, dIds)
    del oIds[0]
    del origins[0]
  else:
    del oIds[0:skip]
    del origins[0:skip]
  
  #The main loop:
  count = 1
  for origwId in zip(oIds, origins):
    fail = singleOrigin(origwId, dests, outfile)
    if fail:
      print 'singleOrigin failed, count = %d, id = %s Status: %s' % (count, origwId[0], fail)
      outfile.close()
      sys.exit()
    count += 1
    if not count % per10s:
      time.sleep(10)
    if not count % perDay:
      time.sleep(60*60*24)
  
  outfile.close()
  
  
  #singleOrigin((oIds[0], origins[0]), dests, sys.stdout, dIds)
  
  #print len(origins)
  #  print dIds
  
  #res = getDist(origins, dests)
  #writeMatrix(res)


# This is the standard boilerplate that calls the main() function.
if __name__ == '__main__':
  main()
